﻿Imports System.Data.SqlServerCe

Namespace Data
    Public Class TransactionScope
        Implements IDisposable

        Private Shared mNextId As Integer
        Private Shared mNextIdSync As New Object
        Private Shared Function GetNextId() As Integer
            SyncLock mNextIdSync
                mNextId += 1
                If mNextId = Integer.MaxValue Then mNextId = 1
                Return mNextId
            End SyncLock
        End Function

        Private mConnection As Connection
        Private mTransaction As SqlCeTransaction
        Private mDataSource As Source
        Public ReadOnly Id As Integer

        ''' <summary>
        ''' Default datasource is Local
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            Me.Id = GetNextId()
        End Sub
        ''' <summary>
        ''' Set explicit datasource
        ''' </summary>
        ''' <param name="dataSource"></param>
        ''' <remarks></remarks>
        Public Sub New(ByVal dataSource As Source)
            Me.Id = GetNextId()
            mDataSource = dataSource
        End Sub

        Public Sub New(ByVal mode As TransactionMode)
            Me.Id = GetNextId()
            mDataSource = Source.Local
            mIsNullTransaction = mode = TransactionMode.NullTransaction
        End Sub

        Public Sub New(ByVal dataSource As Source, ByVal mode As TransactionMode)
            Me.Id = GetNextId()
            mDataSource = dataSource
            mIsNullTransaction = mode = TransactionMode.NullTransaction
        End Sub
        Public Enum TransactionMode
            RealTransaction
            NullTransaction
        End Enum

        Private mIsNullTransaction As Boolean = False
        Public ReadOnly Property IsNullTransaction() As Boolean
            Get
                Return mIsNullTransaction
            End Get
        End Property

        Public ReadOnly Property Connection() As Connection
            Get
                If mConnection Is Nothing Then
                    mConnection = Environment.GetConnection(mDataSource)
                    mConnection.Open()
                End If
                Return mConnection
            End Get
        End Property
        Friend ReadOnly Property CETransaction() As SqlCeTransaction
            Get
                If Not mIsNullTransaction Then
                    If mTransaction Is Nothing Then
                        Environment.OnDBTraceInfoTransactionStart(Me)
                        mTransaction = mConnection.GetCEConnection.BeginTransaction(IsolationLevel.Serializable)
                    End If
                End If
                Return mTransaction
            End Get
        End Property


        Public Sub Complete()
            If mTransaction IsNot Nothing Then
                Environment.OnDBTraceInfoTransactionCommit(Me)
                mTransaction.Commit(CommitMode.Immediate)
                mTransaction = Nothing
            End If
            If mConnection IsNot Nothing Then
                mConnection.Close()
                mConnection = Nothing
            End If
            mFieldAssignments = Nothing
            If mOnCompleteCallbacks IsNot Nothing Then
                CallAllOnCompleteCallbacks()
                mOnCompleteCallbacks = Nothing
                mOnCompleteCallbacksHashed = Nothing
            End If
        End Sub

        Private disposedValue As Boolean = False        ' To detect redundant calls

        ' IDisposable
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    ' TODO: free other state (managed objects).
                End If

                If mFieldAssignments IsNot Nothing Then
                    For Each clsFA As TransactionalFieldAssigner In mFieldAssignments
                        clsFA.Rollback()
                    Next
                    mFieldAssignments.Clear()
                End If
                If mTransaction IsNot Nothing Then
                    Environment.OnDBTraceInfoTransactionRollback(Me)
                    mTransaction.Rollback()
                    mTransaction = Nothing
                End If

                If mConnection IsNot Nothing Then
                    mConnection.Close()
                    mConnection = Nothing
                End If
            End If
            Me.disposedValue = True
        End Sub

#Region " IDisposable Support "
        ' This code added by Visual Basic to correctly implement the disposable pattern.
        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

        Private mFieldAssignments As New Generic.List(Of TransactionalFieldAssigner)
        Public Delegate Sub SetFieldDelegate(Of T)()
        Public Sub SetClassFieldValue(Of T)(ByVal [class] As System.Type, ByVal fieldName As String, ByVal newValue As T)
            Dim clsFA As New TransactionalFieldAssigner([class], fieldName, newValue)
            mFieldAssignments.Add(clsFA)
        End Sub
        Public Sub SetInstanceFieldValue(Of T)(ByVal [object] As Object, ByVal fieldName As String, ByVal newValue As T)
            Dim clsFA As New TransactionalFieldAssigner([object], fieldName, newValue)
            mFieldAssignments.Add(clsFA)
        End Sub

        Private Class CallbackHolder
            Public UniqueId As String
            Public Callback As OnCompletedDelegate
            Public Arg1 As String
            Public Arg2 As Integer
            Public Arg3 As Object
        End Class
        Private mOnCompleteCallbacks As New Generic.List(Of CallbackHolder)
        Private mOnCompleteCallbacksHashed As New Generic.Dictionary(Of String, CallbackHolder)
        Public Delegate Sub OnCompletedDelegate(ByVal arg1 As String, ByVal arg2 As Integer, ByVal arg3 As Object)
        Public Sub RegisterOnCompleteCallback(ByVal callback As OnCompletedDelegate, ByVal arg1 As String, ByVal arg2 As Integer, ByVal arg3 As Object)
            RegisterOnCompleteCallback(callback, arg1, arg2, arg3, True)
        End Sub
        Public Sub RegisterOnCompleteCallback(ByVal callback As OnCompletedDelegate, ByVal arg1 As String, ByVal arg2 As Integer, ByVal arg3 As Object, ByVal mergeDuplicates As Boolean)
            Dim strUniqueId As String = String.Format("{0}/{1}", arg1, arg2)
            If mOnCompleteCallbacksHashed.ContainsKey(strUniqueId) Then
                If Not mergeDuplicates Then Throw New Exception("Duplicates are not allowed!")
                Dim clsCBH As CallbackHolder = mOnCompleteCallbacksHashed(strUniqueId)
                'move it to the end of the list
                mOnCompleteCallbacks.Remove(clsCBH)
                mOnCompleteCallbacks.Add(clsCBH)
            Else
                Dim clsCBH As New CallbackHolder
                clsCBH.Arg1 = arg1
                clsCBH.Arg2 = arg2
                clsCBH.Arg3 = arg3
                clsCBH.Callback = callback
                clsCBH.UniqueId = strUniqueId
                mOnCompleteCallbacks.Add(clsCBH)
                mOnCompleteCallbacksHashed.Add(strUniqueId, clsCBH)
            End If
        End Sub
        Private Sub CallAllOnCompleteCallbacks()
            For Each clsCBH As CallbackHolder In mOnCompleteCallbacks
                With clsCBH
                    .Callback.Invoke(.Arg1, .Arg2, .Arg3)
                End With
            Next
        End Sub


        Public Class TransactionalFieldAssigner
            Private mFI As System.Reflection.FieldInfo
            Private mObject As Object
            Private mFieldIndex As Integer = -1
            Private mOldValue As Object

            Public Sub New(ByVal [class] As System.Type, ByVal fieldName As String, ByVal newValue As Object)
                mFI = [class].GetField(fieldName, Reflection.BindingFlags.Static Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Public)
                mObject = Nothing
                mOldValue = mFI.GetValue(mObject)
                mFI.SetValue(mObject, newValue)
            End Sub
            Public Sub New(ByVal [object] As Object, ByVal fieldName As String, ByVal newValue As Object)
                mObject = [object]
                mFI = [object].GetType.GetField(fieldName)
                If mFI Is Nothing Then
                    mFI = [object].GetType.GetField(fieldName, Reflection.BindingFlags.Instance Or Reflection.BindingFlags.IgnoreCase Or Reflection.BindingFlags.IgnoreReturn Or Reflection.BindingFlags.GetField Or Reflection.BindingFlags.NonPublic)
                End If
                mOldValue = mFI.GetValue(mObject)
                mFI.SetValue(mObject, newValue)
            End Sub

            Public Sub Rollback()
                mFI.SetValue(mObject, mOldValue)
            End Sub
        End Class


    End Class
End Namespace